สร้าง Chatbot แบบง่ายๆด้วย Dialogflow และ Google Sheets + Apps Script
Table of Contents
จะเป็นอย่างไรถ้าเอา Dialogflow ไปเชื่อมต่อกับ Google Apps Script แล้วใช้ Google Sheets เป็น database บทความนี้มีคำตอบครับ
บอกไว้ก่อน #
ก่อนจะเข้าเนื้อหาผมอยากให้ทุกคนศึกษาพื้นฐานของสิ่งเหล่านี้ก่อนนะครับ
- Dialogflow ผมเคยเขียนอธิบายเอาไว้ใน Medium ครับลองไปอ่านตรงนี้ก่อนนะครับ สร้าง Chatbot ร้านอาหารแบบง่ายๆ ด้วย Dialogflow: Part 1
- Google Apps Script และ Clasp หลายคนอาจจะรู้จัก Apps Script แต่อาจจะไม่รู้จัก Clasp ลองเข้าไปอ่านที่นี่ก่อนนะครับ มาเขียน Google Apps Script บน local กันเถอะ (ใช้ modern javascript ได้ด้วยนะ)
โจทย์วันนี้ #
ผมจะสร้าง chatbot ร้านรองเท้าที่แสนจะธรรมดาครับ bot ตัวนี้จะมีหน้าที่เดียวเลยคือบอกราคาของรองเท้าแต่ละรุ่นที่มีในร้านครับ ถ้าไม่มีก็ไม่ตอบ (เอาแบบนี้เลย!) โดยผมจะใช้เครื่องมือยอดนิยมอย่าง Dialogflow ของ Google เป็นตัวกลางในการทำ NLP(Natural Language Processing) แล้วส่งไปประมวลผลกับ Google Apps Script ที่มี Google Sheets เป็นฐานข้อมูลราคาอีกทีครับ
เริ่มจาก Dialogflow #
-
ผมสร้าง Project ก่อนเลยครับ ผมจะให้ชื่อว่า Sheet-Chatbot เลือกภาษา
Thai - th
เพราะเราจะรองรับแค่ภาษาไทย -
เนื่องจากเราจะทำ bot ร้านรองเท้า เราก็ต้องทำให้มันรู้จักชื่อรองเท้าที่ในร้านก่อนครับ ด้วยการสร้าง Entity ขึ้นมาชื่อว่า
product
ครับ -
หลังจากนั้นเราก็จะมาสร้าง Intent ของคนใช้กันครับ แปลง่ายๆก็น่าจะเป็น “ความต้องการ” ของการสื่อสารกับ bot น่ะครับ โดยผมตั้งชื่อว่า
Ask for price
ก็ตรงตัวครับ ถามราคา -
คราวนี้เราก็มาที่ Training Phase ครับ สอนให้ Bot รู้จักรูปประโยคของการ ถามราคา กันหน่อย ตรงนี้ยิ่งใส่เยอะๆยิ่งดีครับ จะเห็นว่าบางประโยคมันจะรู้เลยว่าคำนี้คือ product ของเรา ก็เพราะว่าผมใส่ไว้ใน Entity แล้วนั่นเองครับ
-
ใส่ parameters ของ Intent ด้วยนะครับ บางครั้งเราไม่ได้ใส่ training phase ที่มี entity ติดไปด้วย เราก็จำเป็นต้องมาเพิ่มนะครับ เพื่อให้ bot ถามต่อจนกว่าจะได้ข้อมูลที่ครบถ้วนสมบูรณ์
-
Define Prompts ด้วยนะครับ เพื่อให้ดูไม่เป็น bot จนเกินไปเราสามารถใส่ประโยคคำถามถึงตัว product ได้หลายๆรูปแบบเลย
-
เพื่อทดสอบคร่าวๆ ผมจะใส่ Text Response ตรงๆลงไปก่อนนะครับ เดี๋ยวตรงนี้เราจะมาแก้ทีหลัง
-
ทดสอบดูว่า bot เข้าใจอย่างที่เราอยากให้มันเข้าใจจริงๆหรือยังด้วยการพิมพ์ถามไปใน field ด้านข้างของจอได้เลยครับ สังเกตดูนะครับว่าที่ส่วน Intent จะขึ้นว่า
Ask for price
หรือเปล่า ถ้าเป็นแบบนั้นก็แปลว่ามันเข้าใจคำถามเราแล้ว
คำถามแรกของเรายังไม่มีข้อมูลของตัว product มันเลยต้องถามเราต่ออีกว่าเรา “ดูรุ่นไหนอยู่” ผมก็ตอบไปสั้นๆว่า “next%” ตอนนี้ให้สังเกตที่ parameter กับ value นะครับ เห็น Nike ZoomX Vaporfly NEXT%
ใช่ไหม แบบนี้แปลว่า bot น่าจะเข้าใจคำถามและรู้ด้วยว่าเราถามถึงสินค้าตัวไหน แต่ที่มันตอบไปว่า อันนี้ก็ไม่ทราบ :P
เพราะเราบอกให้มันตอบไปแบบนั้นไงครับ

มาที่ Google Apps Script กับ Google Sheets บ้าง #
- เริ่มจากการ clone Apps Script Starter แล้วก็ติดตั้ง dependency package ก่อนเลยครับ
git clone https://github.com/labnol/apps-script-starter sheet-chatbot
cd sheet-chatbot
npm install
- สร้าง Project บน Apps Script บ้าง โดยเลือก type เป็น
sheets
นะครับ เดี๋ยวเราจะใช้ sheet เก็บข้อมูลราคา
npx clasp create --type sheets --title "Sheet Chatbot" --rootDir ./dist

- หลังจากนั้นก็ใช้ IDE เปิด folder ขึ้นมาครับ อย่างผมใช้ Visual Studio Code ก็แค่พิมพ์
code .
บน terminal แล้วหลังจากนั้นก็ลบไฟล์ตั้งต้นใน foldersrc
ทิ้งให้หมดครับ เหลือไว้แค่index.js
ซึ่งผมแก้เป็นแบบนี้
const helloWorld = () => {
Logger.log('Hello World');
}
global.helloWorld = helloWorld;

- เปิดไฟล์
appsscript.json
ขึ้นมาแล้วก็ลบทุกสิ่งที่ไม่ได้ใช้ทิ้งเช่นกันครับ ผมแก้webapp.access
ให้ANYONE_ANONYMOUS
ด้วยนะ เพราะว่าหลังจากเรา deploy ไปแล้วเราต้องการให้ Dialogflow เข้ามา request ได้ด้วย
appsscript.json
{
"timeZone": "Asia/Calcutta",
"dependencies": {
"libraries": []
},
"webapp": {
"access": "ANYONE_ANONYMOUS",
"executeAs": "USER_DEPLOYING"
},
"exceptionLogging": "STACKDRIVER",
"oauthScopes": []
}
- ทำการ deploy แล้วเข้าไปเช็คที่เว็บด้วยคำสั่ง
npm run deploy
clasp open

-
สั่ง Run function
helloWorld
ลองดูสักหน่อย ถ้าใน Log ขึ้นว่า “Hello World” แปลว่าไม่มีอะไรผิดพลาด -
เพื่อความสะดวกสบายในชีวิต ผมติดตั้ง 3rd party library เข้าไป 2 ตัวนะครับ โดยไปที่ Resources" > “Libraries… แล้วติดตั้งพวกนี้ลงไป
- Tamotsu เอาไว้ mapping ข้อมูลบน Sheet ให้กลายเป็น Object ทำให้เราสะดวกในการเรียกใช้ครับ ไม่จำเป็นต้องไปปวดหัวกับ cell, row, column เลย key ของตัวนี้ก็คือ
1OiJIgWlrg_DFHFYX_SoaEzhFJPCmwbbfEHEqYEfLEEhKRloTNVJ-3U4s
- BetterLog บันทึก log ลงบน Sheet ของเรา บางครั้งที่เรา call function จากข้างนอก web editor เราไม่สามารถเปิดเข้าไปดู log ได้ครับ ตัวนี้จะทำให้เราเช็คได้จาก sheet ของเราแทน key ของตัวนี้ก็คือ
1DSyxam1ceq72bMHsE6aOVeOl94X78WCwiYPytKi7chlg4x5GqiNXSw0l
อย่าลืมเลือก version ล่าสุดกันด้วยล่ะครับ

- หลังจากเพิ่ม Library ใน web editor แล้วก็มาเพิ่มใน
appsscript.json
ด้วยนะ
appsscript.json
{
"timeZone": "Asia/Calcutta",
"dependencies": {
"libraries": [
{
"userSymbol": "BetterLog",
"libraryId": "1DSyxam1ceq72bMHsE6aOVeOl94X78WCwiYPytKi7chlg4x5GqiNXSw0l",
"version": "27"
},
{
"userSymbol": "Tamotsu",
"libraryId": "1OiJIgWlrg_DFHFYX_SoaEzhFJPCmwbbfEHEqYEfLEEhKRloTNVJ-3U4s",
"version": "31"
}
]
},
"webapp": {
"access": "ANYONE_ANONYMOUS",
"executeAs": "USER_DEPLOYING"
},
"exceptionLogging": "STACKDRIVER",
"oauthScopes": []
}
ที่ .eslintrc
ก็ต้องเพิ่ม Library ใหม่ของเราเข้าไปที่ globals
ด้วยนะครับ เดี๋ยว build ไม่ผ่าน
.eslintrc
{
"root": true,
"parser": "babel-eslint",
"extends": [
"eslint:recommended",
"airbnb-base",
"plugin:prettier/recommended"
],
"plugins": [
"prettier",
"googleappsscript"
],
"env": {
"googleappsscript/googleappsscript": true
},
"rules": {
"prettier/prettier": "error",
"import/prefer-default-export": "error"
},
"globals": {
"Tamotsu": true,
"BetterLog": true
}
}
-
เข้าไปที่ https://sheets.google.com แล้วเปิดไฟล์ Sheet Chatbot ที่ clasp สร้างเอาไว้
-
ใส่ข้อมูลของสินค้าลงไปที่ sheet ชื่อ
Products
โดย#
คือรหัสสินค้า นอกจากนั้นก็ตามชื่อ column เลยครับ อย่าลืม copysheet id
ออกมาจาก url ด้วยนะครับ อย่างในรูปของผมคือ1wEvuDUsMDTBIvnkni6A1th1oEehDNVdaTjvUP3gJdLw
-
ได้
sheet id
แล้วก็มาสร้างไฟล์สำหรับตั้งค่าตัว Library ครับ ตรงนี้สามารถเข้าไปอ่านเพิ่มเติมได้ที่ github repository ของตัว library เองเลยนะครับ
src/sheets.config.js
const sheetId = '1wEvuDUsMDTBIvnkni6A1th1oEehDNVdaTjvUP3gJdLw';
// eslint-disable-next-line no-global-assign
Logger = BetterLog.useSpreadsheet(sheetId);
Tamotsu.initialize();
- ลอง deploy แล้ว run
helloWorld
ใหม่อีกรอบ คราวนี้เราจะเห็น Log ไปโผลที่ Sheet ใหม่ชื่อว่า Log ครับ
npm run deploy
clasp open
จะมีขอ permissoin นะครับ ก็ให้อนุญาตการเข้าถึงไปตามปกติ

มี Log มาแสดงบน Sheet แล้ว

- ลองแก้
src/index.js
ให้ทำงานหลังถูก request ด้วย methodPOST
จากข้างนอกดูบ้างครับ โดยผมจะให้มัน Log ค่าที่ Dialogflow ส่ง request มาด้วย (โดยปกติPOST
มันจะเข้ามาที่ functiondoPost
นะครับ ส่วนนี้เป็นของ Apps Script เองเลย)
src/index.js
import './sheets.config';
const doPost = e => {
const data = JSON.parse(e.postData.contents);
Logger.log(JSON.stringify(data));
};
global.doPost = doPost;
- ลอง deploy ใหม่อีกครั้งแล้ว เข้าไปที่ Publish > Deploy as webapp ใน Web editor แล้ว copy เอา Current web app URL: เก็บไว้ครับ
npm run deploy
clasp open

- กลับไปที่ Dialogflow ครับ เลือกที่เมนู Fulfillment ในส่วนของ Webhook ให้เราวาง url ที่ copy มาเมื่อสักครู่ลงไปครับ อย่าลืม scroll ลงไปกดปุ่ม Done ด้วยนะครับ

- ยังอยู่ที่ Dialogflow นะครับ กลับไปเปิด Intent ที่เราสร้างไว้เมื่อช่วงแรกที่ชื่อว่า
Ask for price
(หวังว่าจะยังไม่ลืมนะครับ) แล้ว scroll ลงไปที่ด้านล่างสุดเลย ลบ Text Reponse ที่เราเคยพิมพ์ไว้ออกไป ที่ Fulfillment ให้เปิด “Enable webhook call for this intent” เอาไว้เลยนะครับ

- ทดลองคุยกับ bot เหมือนช่วงแรกเลยครับ แน่นอนว่าเรายังไม่ได้คำตอบกลับมาหรอก

- กลับเปิดที่ Sheet ของเราครับ คราวนี้เราจะได้เห็น request object ที่ถูกส่งมาจาก Dialogflow

มาวิเคราะห์กันดีกว่าว่ามีอะไรส่งมาให้เราดูบ้าง
{
"responseId": "...",
"queryResult": {
"queryText": "next%",
"parameters": {
"product": "Nike ZoomX Vaporfly NEXT%"
},
"allRequiredParamsPresent": true,
"fulfillmentMessages": [
{
"text": {
"text": [
""
]
}
}
],
"intent": {
"name": "projects/sheet-chatbot-unfjpc/agent/intents/74d5284b-e57f-455e-a3ba-fcf7b8439239",
"displayName": "Ask for price"
},
"intentDetectionConfidence": 1,
"languageCode": "th"
},
"originalDetectIntentRequest": {
"payload": {}
},
"session": "..."
}
หลายคนน่าจะมองออกแล้วว่าสิ่งที่เราต้องการตอนนี้มีอะไรบ้าง แต่ใครที่ยังไม่รู้ให้สังเกตที่ queryResult.parameters
กับ queryResult.intent
ครับผม 2 ค่านี้เพียงพอที่จะทำให้เรารู้แล้วว่าคนที่คุยกับ bot ต้องการอะไร ถ้าแปลงให้เห็นชัดๆก็คือ
intent: Ask for price
product: Nike ZoomX Vaporfly NEXT%
ชัดขนาดนี้แล้วอย่าเสียเวลาเลยครับ ตอนนี้เรารู้แล้วว่าจะเขียน response ราคากลับไปหาได้ยังไง
- กลับมาที่ Apps Script เพื่อสร้าง Product model ด้วยความสามารถของ Tamotsu เลยครับ แค่ชี้ไปบอกว่า sheet ชื่ออะไรหลังจากนั้นเราก็สามารถเรียกข้อมูลจาก Product ออกมาได้เหมือน query document ออกมาจาก database เลย
src/product.model.js
const Product = Tamotsu.Table.define({ sheetName: 'Products' });
export default Product;
- มาปรับแต่ง
src/index.js
เพ่ิมเติมเพื่อให้ response กลับไปหา Dialogflow แบบที่มันควรจะเป็นกันหน่อย
//เรียก config และ Product Model
import './sheets.config';
import Product from './product.model';
//เป็นท่ามาตรฐานในการสร้าง JSON Output ของ Apps Script ครับ
const responseJSON = jsonObject => {
return ContentService.createTextOutput(JSON.stringify(jsonObject)).setMimeType(
ContentService.MimeType.JSON
);
};
const doPost = e => {
const data = JSON.parse(e.postData.contents);
//ตรวจสอบ request ว่ามีข้อมูลที่ต้องการไหม
if (!data.queryResult) {
return responseJSON({ fulfillmentText: 'หนูว่ามีปัญหาแล้วอันนี้' });
}
const { parameters, intent } = data.queryResult;
//ตรวจสอบว่า intent เป็นการถามราคาหรือเปล่า (เผื่อมีหลาย intent)
if (intent.displayName === 'Ask for price') {
const productName = parameters.product;
//query เอา product ที่มี name ตรงกับที่ dialogflow ส่งมาให้
const product = Product.where({ name: productName }).first();
//สร้าง fulfillment text เพื่อตอบกลับไปที่ dialoflow
const response = { fulfillmentText: `${product.name} ราคา £${product.price} ค่ะ` };
//ส่งคำตอบกลับไป
return responseJSON(response);
}
//ในการณีที่ไม่เจอ Intent ที่เขียนเอาไว้้
return responseJSON({ fulfillmentText: 'ไม่เข้าใจค่ะ ลองใหม่อีกทีนะคะ' });
};
global.doPost = doPost;
- กลับมาที่ Dialogflow อีกครั้งเพื่อทดสอบครั้งสุดท้าย แต่รอบนี้เพื่อให้เหมือนจริง ผมจะใช้ Web Demo คุยกับ bot ดูบ้าง วิธีเปิดก็ง่ายๆครับไปที่เมนู Integrations แล้วเปิด Web Demo หลังจากเปิดแล้วจะมี popup ขึ้นมาพร้อมกับบอก Url ของ web ให้เราเข้าไปคุยกับ bot ได้ครับ

- แล้ว Bot ก็ตอบราคากลับมาอย่างที่เราตั้งใจไว้ซะที

ถ้าต้องการเอา bot ตัวนี้ไปทำงานบน chat platform ดังๆเช่น LINE, Facebook, Slack ก็สามารถได้นะครับ ที่เมนู Integrations มีบอกครบทุกอย่างเลย แต่ว่าในแต่ละ platform เราก็จำเป็นต้องเขียน fulfillmentMessage
ใน Apps Script ให้ต่างกันออกไปด้วยครับ จากตอนแรกเราส่งไปแค่ fulfillmentText
เช่น
//เพ่ิม fulfillment ของ LINE
const response = {
fulfillmentText: 'ข้อความที่จะตอบกลับแบบปกติ'
fulfillmentMessages: [
{
platform: 'line',
type: 4,
payload: {
line: {
type: 'text',
text: 'ข้อความที่จะตอบกลับใน LINE'
}
}
}
]
}
เสร็จเรียบร้อยไปแล้วนะครับ สำหรับการทำ chatbot อย่างง่ายโดยใช้ Dialogflow และ Google Apps Script + Google Sheets จากตรงนี้เราสามารถต่อยอดได้ด้วยการเพิ่ม Intent เป็นเรื่องต่างๆได้เท่าที่ database ของเราจะมีข้อมูลไว้ตอบกลับไปได้ครับ ซึ่งจริงๆแล้วนอกจาก GAS แล้วเรายังเอาหลักคิดแบบนี้ไปใช้บน Platform อื่นได้หมดเลยนะครับ บทความนี้แค่ทำมาให้คนงกที่ไม่อยากเสียตังค์ค่า server ได้ลองเล่นกันครับ หวังว่าจะมีประโยชน์กับทุกคนที่สนใจการทำ Bot ไม่มากก็น้อยนะครับ มีคำถามสามารถสอบถามเข้ามาที่ Inbox ของ Facebook Page ได้เลยนะครับ